﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Annotations;

namespace CxExtension
{
	public delegate ITaskAgent<T> AgentNewD<T>();
	public delegate void AgentGenerateD<T>(TaskManager<T> manager,int generateNum);
	public class TaskManager<T>
	{
		private readonly Queue<T> mTaskQueue = new Queue<T>(); //
		private readonly Stack<ITaskAgent<T>> mIdleAgents = new Stack<ITaskAgent<T>>(); //
		private IMonitorAgent<object> mUpdateMonitor;
		private bool mAutoAddAgent = false;
		private AgentNewD<T> mAgentNewAction;
		public event AgentGenerateD<T> AgentNewEndEvent;
		/// <summary>
		/// if task count == 0 && RunAgents.Count == 0 then return true
		/// </summary>
		public bool IsIdle => mTaskQueue.Count == 0 && RunAgents.Count == 0;
		public int AgentCount { get; private set; } = 0;
		public LinkedList<ITaskAgent<T>> RunAgents { get; } = new LinkedList<ITaskAgent<T>>();

		public void AddTask(T task)
		{
			mTaskQueue.Enqueue(task);
		}

		public void SetUpdateMonitor(IMonitorAgent<object> v)
		{
			mUpdateMonitor = v;
			v.Init();
		}
		/// <summary>
		/// set the agent action then auto add agent
		/// </summary>
		/// <param name="AgentNewAction"></param>
		public void SetAgentNewAction([NotNull] AgentNewD<T> AgentNewAction)
		{
			if (AgentNewAction == null) throw new ArgumentNullException(nameof(AgentNewAction));
			mAgentNewAction = AgentNewAction;
			mAutoAddAgent = true;
		}

		public void AddAgent(ITaskAgent<T> agent)
		{
			agent.Init();
			mIdleAgents.Push(agent);
			AgentCount++;
		}


		public void Update(float dt)
		{
			//process run
			mUpdateMonitor?.Start(this);

			RunAgents.ForEachSafe(agent => UpdateTaskInternal(agent, dt),RecycleAction);
			if (mAutoAddAgent)
			{
				var waitCount = mTaskQueue.Count - mIdleAgents.Count;
				if (waitCount > 0)
				{
					for (var i = 0; i < waitCount; i++)
					{
						var it = mAgentNewAction();
						AddAgent(it);
					}

					if (AgentNewEndEvent != null) AgentNewEndEvent(this, waitCount);
				}
			}
			while (mTaskQueue.HasItem() && mIdleAgents.HasItem())
			{
				var node = StartTaskInternal();
				var agent = node.Value;
				var isComplete = UpdateTaskInternal(agent, dt);
				if (isComplete)
				{
					if (RecycleAction(node))
					{
						break;
					}
				}
			}
		}

		#region TaskLogic
		private LinkedListNode<ITaskAgent<T>> StartTaskInternal()
		{
			var task = mTaskQueue.Dequeue();
			var agent = mIdleAgents.Pop();
			var node = RunAgents.AddLast(agent);
			agent.Start(task);
			return node;
		}

		private bool UpdateTaskInternal(ITaskAgent<T> agent,float dt)
		{
			var isComplete = agent.Update(dt);
			if (isComplete)
			{
				agent.End();
				agent.Reset();
			}
			return isComplete;
		}
		private bool RecycleAction(LinkedListNode<ITaskAgent<T>> node)
		{
			RunAgents.Remove(node);
			mIdleAgents.Push(node.Value);
			//monitor code
			var result = false;
			if (mUpdateMonitor != null)
			{
				result = mUpdateMonitor.Update(0);
			}
			return result;
		}
		#endregion
	}
}